/**
 * 
 */
package org.statewalker.fsm.providers;

import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * @author kotelnikov
 */
public class FsmStateDescriptor<S, E> {

    private S fFinalStateKey;

    private S fInitialStateKey;

    private FsmStateDescriptor<S, E> fNullDescriptor;

    private boolean fRootDescriptor;

    private Map<S, FsmStateDescriptor<S, E>> fSubstateDescriptors = new HashMap<S, FsmStateDescriptor<S, E>>();

    private Map<S, Map<E, S>> fTransitionMap = new HashMap<S, Map<E, S>>();

    /**
     * 
     */
    public FsmStateDescriptor(boolean root, S initialStateKey, S finalStateKey) {
        super();
        fInitialStateKey = initialStateKey;
        fFinalStateKey = finalStateKey;
        fRootDescriptor = root;
    }

    /**
     * @param substateKey
     * @param descriptor
     */
    public void addSubstateDescriptor(
        S substateKey,
        FsmStateDescriptor<S, E> descriptor) {
        fSubstateDescriptors.put(substateKey, descriptor);
    }

    /**
     * @param descriptor
     */
    public void addTransition(S from, E event, S to) {
        from = filter(from, fInitialStateKey);
        to = filter(to, fFinalStateKey);
        Map<E, S> transitions = fTransitionMap.get(from);
        if (transitions == null) {
            transitions = new LinkedHashMap<E, S>();
            fTransitionMap.put(from, transitions);
        }
        transitions.put(event, to);
    }

    /**
     * @see java.lang.Object#equals(java.lang.Object)
     */
    @Override
    public boolean equals(Object obj) {
        if (obj == this)
            return true;
        if (!(obj instanceof FsmStateDescriptor<?, ?>))
            return false;
        FsmStateDescriptor<?, ?> descriptor = (FsmStateDescriptor<?, ?>) obj;
        return fTransitionMap.equals(descriptor.fTransitionMap)
            && fSubstateDescriptors.equals(descriptor.fSubstateDescriptors);
    }

    private S filter(S key, S defaultKey) {
        return key != null ? key : defaultKey;
    }

    /**
     * Returns the key of the target sub-state for the transition defined by the
     * given initial state key and the event key.
     * 
     * @param stateKey the key of the initial state of the transition
     * @param eventKey the key of the event activating the transition
     * @return the key of the target sub-state for the transition defined by the
     *         given initial state key and the event key.
     */
    public S getNextSubstateKey(S stateKey, E eventKey) {
        stateKey = filter(stateKey, fInitialStateKey);
        Map<E, S> transitions = fTransitionMap.get(stateKey);
        if (transitions == null)
            return null;
        S to = transitions.get(eventKey);
        return to;
    }

    private FsmStateDescriptor<S, E> getNullDescriptor() {
        if (!fRootDescriptor)
            return null;
        if (fNullDescriptor == null) {
            fNullDescriptor = new FsmStateDescriptor<S, E>(
                true,
                fInitialStateKey,
                fFinalStateKey);
        }
        return fNullDescriptor;
    }

    /**
     * Returns a descriptor of a sub-state corresponding to the given key.
     * 
     * @param substateKey the key of the sub-state for which the corresponding
     *        descriptor should be returned
     * @return a descriptor of a sub-state corresponding to the specified key
     */
    public FsmStateDescriptor<S, E> getSubstateDescriptor(S substateKey) {
        FsmStateDescriptor<S, E> descriptor = fSubstateDescriptors
            .get(substateKey);
        if (descriptor == null && substateKey != null) {
            if (fFinalStateKey == null || !fFinalStateKey.equals(substateKey)) {
                descriptor = getNullDescriptor();
            }
        }
        return descriptor;
    }

    /**
     * @see java.lang.Object#hashCode()
     */
    @Override
    public int hashCode() {
        int a = fSubstateDescriptors.hashCode();
        int b = fTransitionMap.hashCode();
        return a ^ b >>> 32;
    }

    /**
     * @see java.lang.Object#toString()
     */
    @Override
    public String toString() {
        return fTransitionMap.toString();
    }

}
